#include "tabsscrollarea.h"

#include <QMouseEvent>
#include <QMimeData>

#include <QApplication>
#include <QHBoxLayout>
#include <QDrag>

#include <QPainter>
#include <QGraphicsDropShadowEffect>
#include <QPlainTextEdit>
#include <utility>

#include "dropmanager.h"
#include "toolutils.h"
#include "pageswidget.h"

TabsScrollArea::TabsScrollArea(QWidget* parent)
    : QScrollArea(parent)
{
    this->Init();
}

TabsScrollArea::~TabsScrollArea()
{
    this->Finalize();
}


void TabsScrollArea::Init()
{
    tabsWidget = new QWidget(this);
    mTabsLayout = new QHBoxLayout(tabsWidget);
    mTabsLayout->setAlignment(Qt::AlignLeft);
    mTabsLayout->setContentsMargins(0, 0, 0, 0);
    this->setWidget(tabsWidget);
    this->setFrameShape(QFrame::Shape::NoFrame);
    this->setFrameShadow(QFrame::Shadow::Plain);
    this->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
    this->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
    this->setSizeAdjustPolicy(QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents);
    this->setAlignment(Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter);
    this->setContentsMargins(0, 0, 0, 0);
    this->setWidgetResizable(true);
    this->setAcceptDrops(true);
    // 将 TabsScrollArea 绑定到DropManager里面去
    this->viewport()->installEventFilter(DropManager::GetInstance());
}

void TabsScrollArea::Finalize() const {
    mTabsLayout->deleteLater();
    tabsWidget->deleteLater();
}

std::pair<int, int> TabsScrollArea::Id()
{
    return mId;
}

std::pair<DragableTab*, QWidget*> TabsScrollArea::Page(const int index) const {
    return index < 0 ? std::make_pair(nullptr, nullptr) : mPages.at(index);
}

std::pair<DragableTab*, QWidget*> TabsScrollArea::Page(const DragableTab *tab) const {
    return Page(PageAt(tab));
}

int TabsScrollArea::PagesCount() const
{
    return mPagesCount;
}

int TabsScrollArea::CurrentIndex() const
{
    return mCurrentIndex;
}

int TabsScrollArea::PageAt(const DragableTab *tab) const {
    for (int i = 0; i < mPagesCount; ++i) {
        if (mPages.at(i).first == tab) {
            return i;
        }
    }
    return -1;
}

// emit pageAdded
void TabsScrollArea::AddPage(DragableTab* tab, QWidget* content)
{
    if (tab == nullptr || content == nullptr) {
        return;
    }
    content->setFocusPolicy(Qt::StrongFocus);
    mPages.append(std::make_pair(tab, content));
    ++mPagesCount;
    tab->SetTabsScrollArea(this);
    tab->setAcceptDrops(true);
    tab->setParent(this);
    tab->SetIndex(mId.first, mId.second, mPagesCount-1);
    content->setParent(this);
    content->installEventFilter(tab);
    mTabsLayout->addWidget(tab);

    tab->show();
    ChangeCurrentPage(tab);
    // debug
    auto index = tab->GetIndex();
    qDebug("TabsScrollArea::AddPage, tabIndex=(%d, %d, %d)", std::get<0>(index), std::get<1>(index), std::get<2>(index));
    // debug#end
    TabConnect(tab);
    emit pageAdded(content);
}

// emit pageAdded
void TabsScrollArea::AddPage(QString titleLabel, QWidget *content, DragableTab::TabType tabType)
{
    auto tab = new DragableTab(std::move(titleLabel), tabType, this);
    AddPage(tab, content);
}

std::pair<QWidget*, QWidget*> TabsScrollArea::SwapPage(int from, int to)
{
    qDebug("TabsScrollArea::SwapPage, from=%d, to=%d, count=%d", from, to, mPagesCount);
    if (from == to || from < 0 || from >= mPagesCount || to < 0 || to >= mPagesCount) {
        qDebug("TabsScrollArea::SwapPage, index is out of range.");
        return std::make_pair(nullptr, nullptr);
    }
    auto fromPage = Page(from);
    auto toPage = Page(to);
    fromPage.first->SetIndex(mId.first, mId.second, to);
    toPage.first->SetIndex(mId.first, mId.second, from);
    // swap
    // 需要优化，没有考虑到一些特殊情况
    // 1、当swap位置的index > page - 2时  --> 已解决，针对from和to的位置先后插入
    // ui->tabsLayout->removeWidget(fromPage.first);
    mTabsLayout->removeWidget(fromPage.first);


    // 这一部分需要pagesWidgets的layout自行去处理
    mTabsLayout->removeWidget(toPage.first);
    // from > to / from < to
    if (from > to) {
        mTabsLayout->insertWidget(to, fromPage.first);
        mTabsLayout->insertWidget(from, toPage.first);
    } else {
        mTabsLayout->insertWidget(from, toPage.first);
        mTabsLayout->insertWidget(to, fromPage.first);
    }
    // end
    mPages.swapItemsAt(from, to);
    ChangeCurrentPage(fromPage.first);
    return std::make_pair(fromPage.second, toPage.second);
}

std::pair<DragableTab*, QWidget*> TabsScrollArea::ReplacePage(DragableTab* fromTab, DragableTab* toTab, QWidget* toContent)
{
    auto index = PageAt(fromTab);
    if (PageAt(fromTab) < 0) {
        return std::pair(nullptr, nullptr);
    }
    auto page = Page(fromTab);
    mTabsLayout->replaceWidget(fromTab, toTab);

    toTab->setAcceptDrops(true);
    toTab->SetIndex(mId.first, mId.second, index);
    toTab->SetTabsScrollArea(this);
    toTab->setParent(this);
    toContent->setParent(this);
    toContent->installEventFilter(toTab);
    toContent->setFocusPolicy(Qt::StrongFocus);
    mPages.replace(index, std::pair(toTab, toContent));

    // clear page index
    page.first->SetIndex(mId.first, -1, -1);
    return page;
}

// emit pageAdded
void TabsScrollArea::InsertPage(const int index, DragableTab* tab, QWidget* content)
{
    if (tab == nullptr || content == nullptr) {
        qDebug() << "TabsScrollArea::InsertPage, tab == nullptr || content == nullptr";
        return;
    }

    for (int var = index; var < mPagesCount; ++var) {
        mPages.at(var).first->SetIndex(mId.first, mId.second, var+1);
    }

    mPages.insert(index, std::make_pair(tab, content));
    ++mPagesCount;
    tab->setAcceptDrops(true);
    tab->setParent(this);
    tab->SetTabsScrollArea(this);
    tab->SetIndex(mId.first, mId.second, index);
    content->setParent(this);
    content->installEventFilter(tab);
    content->setFocusPolicy(Qt::StrongFocus);
    mTabsLayout->insertWidget(index, tab);

    ChangeCurrentPage(tab);
    TabConnect(tab);
    emit pageAdded(content);
}

// emit pageAdded
void TabsScrollArea::InsertPage(int index, QString titleLabel, DragableTab::TabType tabType, QWidget *content)
{
    auto tab = new DragableTab(std::move(titleLabel), tabType, this);
    InsertPage(index, tab, content);
}

int TabsScrollArea::CurrentPageIndex() const
{
    return mCurrentIndex;
}

std::pair<DragableTab*, QWidget*> TabsScrollArea::CurrentPage() const
{
    return mCurrentPage;
}

// emit pageRemoved
std::pair<DragableTab*, QWidget*> TabsScrollArea::RemovePage(const int index)
{
    if (index < 0 || index >= mPagesCount) {
        return std::make_pair(nullptr, nullptr);
    }
    qDebug("TabsScrollArea::RemovePage, mCurrentIndex=%d, mPagesCount=%d", mCurrentIndex, mPagesCount);
    for (int var = index; var < mPagesCount; ++var) {
        if (var == index) {
            continue;
        }
        mPages.at(var).first->SetIndex(mId.first, mId.second, var-1);
    }
    auto page = mPages.at(index);
    mPages.removeAt(index);
    --mPagesCount;
    if (mCurrentIndex > index) {
        --mCurrentIndex;
    } else if (mCurrentIndex == index) {
        mCurrentIndex = mPagesCount - 1;
        mCurrentPage = mCurrentIndex >= 0 ? mPages.back() : std::make_pair(nullptr, nullptr);
    }
    if (mPagesCount <= 0) {
        this->close();
        return page;
    }
    // 将widget从layout里面删除
    mTabsLayout->removeWidget(page.first);

    TabDisconnect(page.first);
    mCurrentPage.second->show();
    qDebug("TabsScrollArea::RemovePage, ChangeCurrentPage");
    ChangeCurrentPage(mCurrentPage.first);
    emit pageRemoved(page.second);
    return page;
}

// emit pageRemoved
std::pair<DragableTab*, QWidget*> TabsScrollArea::RemovePage(const DragableTab *tab)
{
    const auto index = PageAt(tab);
    qDebug("PagesWidget::RemovePage, index=%d", index);
    return RemovePage(index);
}

void TabsScrollArea::Clear()
{
    // TODO: 有优化可能，对于组件相关的回收还有点不清晰
    foreach(const auto page, mPages) {
        TabDisconnect(page.first);
    }
    mPages.clear();
    this->close();
}

void TabsScrollArea::setCurrentPage(DragableTab *tab)
{
    const auto index = PageAt(tab);
    if (index == -1) {
        return;
    }
    setCurrentIndex(index);
}

// emit currentPageChanged
void TabsScrollArea::setCurrentIndex(int index)
{
    const auto page = mPages.at(index);
    mCurrentPage.second->hide();
    page.second->show();
    mCurrentIndex = index;
    mCurrentPage = page;

    emit currentPageChanged(page.second);
}

// protected

void TabsScrollArea::SetId(int dId, int pId)
{
    mId = std::make_pair(dId, pId);
    foreach (auto page, mPages) {
        page.first->SetIndex(dId, pId, std::get<2>(page.first->GetIndex()));
    }
}

void TabsScrollArea::SetDroppableWidget(DroppableWidget* widget)
{
    mDroppableWidget = widget;
}

void TabsScrollArea::SetParentPagesWidget(PagesWidget* pagesWidget)
{
    mParentPagesWidget = pagesWidget;
}

PagesWidget* TabsScrollArea::ParentPagesWidget() const
{
    return mParentPagesWidget;
}

// 待定
void TabsScrollArea::closeEvent(QCloseEvent *event)
{
    // 在这里添加关闭窗口时的处理逻辑
    qDebug() << "PagesWidget::closeEvent, 窗口被关闭";
    emit closed();
    event->accept();
}

void TabsScrollArea::mousePressEvent(QMouseEvent *event)
{
    // qDebug("DragableTab::mousePressEvent");
    if (!(event->buttons() & Qt::LeftButton)) {
        QScrollArea::mousePressEvent(event);
        return;
    }
    mDragStartPos = event->pos();
    mDragReady = true;
    this->update();
    QScrollArea::mousePressEvent(event);
}

void TabsScrollArea::mouseMoveEvent(QMouseEvent *event)
{
    // TODO: tabsScrollArea的整体拖拽过程
    if (!mDragReady || !(event->buttons() & Qt::LeftButton)) {
        QScrollArea::mousePressEvent(event);
        return;
    }

    // 小于系统定制的拖拽最小distance
    if ((event->pos() - mDragStartPos).manhattanLength() < QApplication::startDragDistance()) {
        QWidget::mouseMoveEvent(event);
        return;
    }
    event->accept();
    mDragReady = false;
    // 建立一个Qdrag拖拽过程
    auto drag = new QDrag(this);
    auto mimeData = new QMimeData();
    // 只需要传递 <DIndex, PIndex> 即可
    mimeData->setData("application/tabsScrollArea-data", QByteArray(
        std::to_string(mId.first) + "," + std::to_string(mId.second)));
    drag->setMimeData(mimeData);

    // 后面优化一下drag窗口

    const auto tab = this->CurrentPage().first;
    QPixmap pixmap(tab->size());
    pixmap.fill(QColor::fromRgb(60, 60, 60));
    QPainter painter(&pixmap);
    painter.drawRect(pixmap.rect());
    const auto dragText = tab->TitleLabel()->text() + QString::asprintf(" (+%d)", this->PagesCount());
    painter.setPen(Qt::white);
    painter.drawText(tab->rect(), Qt::AlignCenter, dragText);
    drag->setPixmap(pixmap);
    drag->setHotSpot(QPoint(0, 0));

    // 添加自定义视觉效果，对于整个组件来说
    auto shadowEffect = new QGraphicsDropShadowEffect(this);
    shadowEffect->setBlurRadius(15);
    shadowEffect->setColor(Qt::black);
    this->viewport()->setGraphicsEffect(shadowEffect);

    Qt::DropAction result = drag->exec(Qt::MoveAction);
    this->viewport()->setGraphicsEffect(nullptr);

    if (result == Qt::MoveAction) {
        return;
    }
    if (ToolUtils::IsCursorInWindow()) {
        // 在主窗口内，但是区域无法接受拖拽，return
        return;
    }
    // 在主窗口外，需要悬浮窗口
    emit NeedToFlatAllPages();
    // 1、将tabsScrollArea整个移动到另一个tabsScrollArea中，全部移动到新的pagesWidget里面 √
    // 在PagesDropIn最后clear掉dragTabsSrollArea中的pages
    // 2、将tabsScrollArea整个移动到droppableWidget中，移动pagesWidget节点即可 √
}

void TabsScrollArea::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() != Qt::LeftButton) {
        return;
    }
    mDragReady = false;
    this->update();
    QScrollArea::mouseReleaseEvent(event);
}

void TabsScrollArea::dragEnterEvent(QDragEnterEvent *event)
{
    qDebug() << "TabsScrollArea::dragEnterEvent";
    if (event->mimeData()->hasFormat("application/tab-data") || event->mimeData()->hasFormat("application/tabsScrollArea-data")) {
        event->acceptProposedAction();
    }
    QScrollArea::dragEnterEvent(event);
}

// private

void TabsScrollArea::TabConnect(DragableTab *tab) const
{
    connect(tab, &DragableTab::dragOut, this, &TabsScrollArea::RemoveDragOutPage);
    connect(tab, &DragableTab::clicked, this, &TabsScrollArea::ClickTab);
    connect(tab, &DragableTab::closeFromButton, this, &TabsScrollArea::CloseTabFromButton);
    connect(tab, &DragableTab::NeedFlatPage, this, &TabsScrollArea::NotifyParentWidgetToFlatPage);
    // 绑定DropManager
    tab->installEventFilter(DropManager::GetInstance());
}

void TabsScrollArea::TabDisconnect(DragableTab *tab) const
{
    disconnect(tab, &DragableTab::dragOut, this, &TabsScrollArea::RemoveDragOutPage);
    disconnect(tab, &DragableTab::clicked, this, &TabsScrollArea::ClickTab);
    disconnect(tab, &DragableTab::closeFromButton, this, &TabsScrollArea::CloseTabFromButton);
    disconnect(tab, &DragableTab::NeedFlatPage, this, &TabsScrollArea::NotifyParentWidgetToFlatPage);
}

void TabsScrollArea::ChangeCurrentPage(DragableTab* tab)
{
    const int index = PageAt(tab);
    if (tab == nullptr || index < 0) {
        return;
    }
    const auto page = Page(tab);
    // qDebug("PagesWidget::ChangeCurrentPage, page=%s", tab->TitleLabel()->text().toStdString().c_str());
    if (tab == mCurrentPage.first) {
        mCurrentPage.first->setStyleSheet(ToolUtils::ReadStyleSheet(":/qss/currentTabUi.qss"));
        mCurrentIndex = index;
        emit currentPageChanged(page.second);
        return;
    }
    // TODO: first需要变化背景色，使其处于未触发状态
    // TODO: 焦点给所在的 widget
    if (page.first == nullptr || page.second == nullptr) {
        return;
    }
    if (mCurrentPage.second != nullptr) {
        mCurrentPage.first->setStyleSheet("");
        mCurrentPage.second->hide();
    }
    page.second->show();
    page.first->setStyleSheet(ToolUtils::ReadStyleSheet(":/qss/currentTabUi.qss"));
    if (mCurrentPage.first != nullptr) {
        mCurrentPage.first->setStyleSheet(ToolUtils::ReadStyleSheet(":/qss/defaultTabUi.qss"));
    }
    mCurrentPage = page;
    mCurrentIndex = index;
    emit currentPageChanged(page.second);
}

// public slots
bool TabsScrollArea::SinglePageDropIn(QPoint dropPos,
    std::tuple<int, int, int> dragIndex, std::tuple<int, int, int> dropIndex)
{
    // dropPos是绝对坐标
    Q_UNUSED(dropPos);
    // debug
    // qDebug("TabsScrollArea::SinglePageDropIn, dropPos=(%d, %d), dragIndex=(%d, %d, %d), dropIndex=(%d, %d, %d)",
    //        dropPos.x(), dropPos.y(), std::get<0>(dragIndex), std::get<1>(dragIndex), std::get<2>(dragIndex),
    //        std::get<0>(dropIndex), std::get<1>(dropIndex), std::get<2>(dropIndex));
    // debug#end
    // TODO: 这个地方需要修改, 从另一个droppable 移动 √
    // 1、还需有tabs之间左右之分，还是得进一步优化
    // 2、在tabs区域外、scrollArea之内的区域，需要放到最后面去 √

    // 坐标判断的规则
    // 1、当drag是从其他pagesWidget来的或是当前pagesWidget但是dragIndex > dropIndex，则按照在中心点后++来
    // 2、当drag是从当前pagesWidget来的，但是dragIndex < dropIndex，则按照在中心点前--来

    int index = std::get<2>(dropIndex);
    if (const auto tab = this->Page(std::get<2>(dropIndex)).first; tab != nullptr) {
        dropPos = tab->mapFromGlobal(dropPos);
        if (std::get<0>(dragIndex) != mId.first || std::get<1>(dragIndex) != mId.second || std::get<2>(dragIndex) > index) {
            index = tab->rect().center().x() < dropPos.x() ? index + 1 : index;
        } else {
            index = tab->rect().center().x() > dropPos.x() ? index - 1 : index;
        }
        qDebug("dropTab.centerX=%d, dropPosX=%d", tab->rect().center().x(), dropPos.x());
    }

    if (std::get<0>(dragIndex) == mId.first && std::get<1>(dragIndex) == mId.second) {
        if (std::get<2>(dropIndex) == -1) {
            SwapPage(std::get<2>(dragIndex), mPagesCount - 1);
        } else {
            SwapPage(std::get<2>(dragIndex), index);
        }
        return true;
    } else {
        // 1、找到dragPage
        auto page = DropManager::GetInstance()->FindSinglePage(
            std::get<0>(dragIndex), std::get<1>(dragIndex), std::get<2>(dragIndex));
        // 2、插入即可
        if (std::get<2>(dropIndex) == -1) {
            InsertPage(mPagesCount, page.first, page.second);
        } else {
            InsertPage(index, page.first, page.second);
        }
        return true;
    }
    return false;
}

bool TabsScrollArea::PagesDropIn(QPoint dropPos, std::pair<int, int> dragIndex, std::tuple<int, int, int> dropIndex)
{
    // debug
    qDebug("TabsScrollArea::PagesDropIn, dropPos=(%d, %d), dragIndex=(%d, %d), dropIndex=(%d, %d, %d)",
           dropPos.x(), dropPos.y(), dragIndex.first, dragIndex.second,
           std::get<0>(dropIndex), std::get<1>(dropIndex), std::get<2>(dropIndex));
    // debug#end
    if (dragIndex.first == std::get<0>(dropIndex) && dragIndex.second == std::get<1>(dropIndex)) {
        return false;
    }
    // 获取拖拽控件
    const auto pagesWidget = DropManager::GetInstance()->FindPagesWidget(dragIndex.first, dragIndex.second);
    auto dragTabsScrollArea = pagesWidget->GetTabsScrollArea();
    // 将scrollArea里面的tabs全部插入进去
    int index = std::get<2>(dropIndex);
    if (index == -1) {
        index = mPagesCount;
    }
    qDebug() << "dragTabsScrollArea->count=" << dragTabsScrollArea->mPagesCount;
    foreach (auto page, dragTabsScrollArea->mPages) {
        InsertPage(index, page.first, page.second);
    }
    dragTabsScrollArea->Clear();
    return true;
}

void TabsScrollArea::RemoveDragOutPage(std::tuple<int, int, int> dragIndex)
{
    const auto dragTab = qobject_cast<DragableTab*> (sender());

    // debug
    const auto index = dragTab->GetIndex();
    qDebug("TabsScrollArea::RemoveDragOutPage(%d, %d), dragIndex=(%d, %d, %d), dropIndex=(%d, %d, %d)",
           mId.first, mId.second,
           std::get<0>(dragIndex), std::get<1>(dragIndex), std::get<2>(dragIndex),
           std::get<0>(index), std::get<1>(index), std::get<2>(index));
    // debug#end

    if (std::get<0>(dragIndex) == mId.first && std::get<1>(dragIndex) == mId.second) {
        qDebug("TabsScrollArea::RemoveDragOutPage(%d, %d), index.first == mId", mId.first, mId.second);
        return;
    }
    RemovePage(dragTab);
}

// private slots

void TabsScrollArea::NotifyParentWidgetToFlatPage(std::tuple<int, int, int> index)
{
    qDebug() << "TabsScrollArea::NotifyParentWidgetToFlatPage, id=" << mId;
    emit NeedToFlatPage(std::get<2>(index));
}

void TabsScrollArea::CloseTabFromButton()
{
    qDebug() << "TabsScrollArea::CloseTabFromButton";
    auto closeTab = qobject_cast<DragableTab*>(sender());
    // 判断是否需要保存或者另存为，需要扩展不同格式的，暂时先写死成txt
    if (closeTab->ContentType() == DragableTab::TabType::TEXT) {
        auto textEdit = qobject_cast<QPlainTextEdit*>(this->Page(closeTab).second);
        // 这里对于修改的判断需要改一下，不应该简单地理解为text为空，而是是否修改过
        if (textEdit->toPlainText().isEmpty() || !ToolUtils::EasyDialogReturnBool("提示", "当前修改页面是否需要保存?")) {
            auto page = RemovePage(closeTab);
            page.first->close();
            page.second->close();
            page.first->deleteLater();
            page.second->deleteLater();
            return;
        }
        if (ToolUtils::SavePage(Page(closeTab), parentWidget())) {
            auto page = RemovePage(closeTab);
            page.first->close();
            page.second->close();
            page.first->deleteLater();
            page.second->deleteLater();
        }
    } else {
        auto page = RemovePage(closeTab);
        page.first->close();
        page.second->close();
        page.first->deleteLater();
        page.second->deleteLater();
    }
}

void TabsScrollArea::ClickTab()
{
    auto clickedTab = qobject_cast<DragableTab*>(sender());
    ChangeCurrentPage(clickedTab);
}
